/*
 * Copyright (C) 2011 Moritz Schmale <narrow.m@gmail.com>
 *
 * DropChest is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/gpl.html>.
 */

package me.scotty86.DropChest;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.logging.Level;

import me.scotty86.DropChest.api.DropChestFillEvent;

import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.material.MaterialData;
import org.bukkit.World;
import org.bukkit.WorldCreator;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.Chest;
import org.bukkit.entity.Player;
import org.bukkit.entity.minecart.StorageMinecart;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.PluginManager;

public class DropChestItem implements Serializable {
	private static final long serialVersionUID = 2940482362395614394L;
	
	private Chest containerBlock;
	
	private int radius;
	private int delay = 0;
	private String owner = "";
	private boolean protect = false;
	private String name = "";
	private int id;
	private HashMap<FilterType, List<FilterItem>> filter = new HashMap<FilterType, List<FilterItem>>();
	
	private boolean warnedNearlyFull = false;
	private DropChest plugin;
	private DropChestMinecartAction minecartAction = DropChestMinecartAction.IGNORE;
	private boolean loadedProperly = true;
	private static int currentId = 1;
	private Location loc = null;
	private long lastRedstoneDrop = 0L;

	public DropChestItem(Chest containerBlock, int radius, Block block, DropChest plugin) {
		this.containerBlock = containerBlock;
		this.radius = radius;
		this.plugin = plugin;
		this.loc = new Location(block.getWorld(), block.getX(), block.getY(), block.getZ());
		this.id = (currentId++);
		initFilter();
		
		List<FilterItem> filter = this.getFilter(FilterType.SUCK);
		filter.add(FilterItem.allItem);
	}

	public DropChestItem(String loadString, String fileVersion, DropChest plugin) {
		this.plugin = plugin;
		initFilter();
		load(loadString, fileVersion);
	}

	public DropChestItem(Map<String, Object> loadMap, DropChest plugin) {
		this.plugin = plugin;
		initFilter();
		load(loadMap);
	}

	private void initFilter() {
		List<FilterItem> f = new ArrayList<FilterItem>();
		this.filter.put(FilterType.SUCK, f);
		f = new ArrayList<FilterItem>();
		this.filter.put(FilterType.PUSH, f);
		f = new ArrayList<FilterItem>();
		this.filter.put(FilterType.PULL, f);
	}

	public Chest getChest() {
		if ((getBlock().getState() instanceof Chest)) {
			this.containerBlock = ((Chest) getBlock().getState());
			return this.containerBlock;
		}
		return null;
	}

	public Inventory getInventory() {
		//First, check if this is a double chest.
		/*
		BlockFace[] faces = { BlockFace.NORTH, BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST };
		for (BlockFace face : faces) {
			Block other = getBlock().getRelative(face);
			if ((other.getType().equals(Material.CHEST)) && ((other.getState() instanceof Chest))) {
				Inventory[] inventories = { this.getChest().getInventory(), ((Chest) other.getState()).getInventory() };
				this.plugin.log.log(Level.WARNING, "DC-Debug #"+id+": " + other.getX() + "/" + other.getZ());
				return new DropChestInventory(inventories);
			}
		}
		*/
		return getChest().getInventory();
	}

	public void setChest(Chest chest) {
		this.containerBlock = chest;
	}

	public int getRadius() {
		return this.radius;
	}

	public void setRadius(int radius)
	{
		setRadius(radius, false);
	}
	public void setRadius(int radius, boolean initialLoading) {
		if(initialLoading == false)
		{
			if (radius < 0) {
				getOwnerPlayer().sendMessage(ChatColor.RED + "Chest radius was negative!, radius changed to 2");
				radius = 2;
			} else if (radius == 0) {
				try
				{
					getOwnerPlayer().sendMessage(ChatColor.RED.toString() + "Chest radius is 0, the chest will not suck");
				}
				catch (Exception e)
				{
					//System.out.println("===================> Error");
				}
			}
		}
		this.radius = radius;
	}

	public void setDelay(int delay) {
		this.delay = (delay >= 0 ? delay : 0);
	}

	public int getDelay() {
		return this.delay;
	}

	@SuppressWarnings("deprecation")
	public Player getOwnerPlayer() {
		return Bukkit.getServer().getPlayer(getOwner());
	}

	@SuppressWarnings("deprecation")
	public double getPercentFull() {
		Inventory inv;
		try {
			inv = getInventory();
		} catch (NullPointerException e) {
			if (getBlock().getChunk().isLoaded()) {
				this.plugin.removeChest(this);
				this.plugin.log.log(Level.WARNING, "Removed 1 Chest because it isn't there any more.");
			}
			return 0.0D;
		}
		// TODO: FEHLER
		int stacks = inv.getSize();
		
		double maxStackSize = 0;
		double totalItems = 0;
		for (int j = 0; j < stacks; j++) {
			ItemStack istack = inv.getItem(j);
			if (istack != null) {
				totalItems += istack.getAmount();
				if (istack.getTypeId() == 0)
					maxStackSize += 64;
				else {
					maxStackSize += istack.getMaxStackSize();
				}
			}
			else
				maxStackSize += 64;
		}
		
		double percent = totalItems / maxStackSize ;
		//this.plugin.log.log(Level.INFO, "DC-Debug #"+id+": " + percent);
		if (percent < this.plugin.config.getWarnFillStatus() / 100.0D)
			this.warnedNearlyFull = false;
		return percent;
	}

	@SuppressWarnings("deprecation")
	public void warnNearlyFull() {
		if ((!this.warnedNearlyFull) && (this.plugin.config.getWarnFillStatus() != -1.0D)) {
			String warnString = this.plugin.config.getWarnString();
			warnString = warnString.replaceAll("\\$name", getName());
			warnString = warnString.replaceAll("\\$fill", String.valueOf((int) (getPercentFull() * 100.0D)));
			warnString = warnString.replaceAll("\\$owner", getOwner());
			Player player = this.plugin.getServer().getPlayer(getOwner());
			if ((player != null) && (player.isOnline()))
				player.sendMessage(warnString);
			else {
				this.plugin.getServer().broadcastMessage(warnString);
			}
			this.warnedNearlyFull = true;
		}
	}

	public List<FilterItem> getFilter(FilterType filterType) {
		return filter.get(filterType);
	}
	
	public HashMap<Integer, ItemStack> addItem(ItemStack item, FilterType filterType) {
		DropChestFillEvent fillEvent = new DropChestFillEvent(this);
		PluginManager pm = this.plugin.getServer().getPluginManager();
		getChest();

		for (FilterItem m : (List<FilterItem>) this.filter.get(filterType)) {
			if (m.equals(item.getData())) {
				pm.callEvent(fillEvent);
				return getInventory().addItem(new ItemStack[] { item });
			}
		}
		HashMap<Integer, ItemStack> ret = new HashMap<Integer, ItemStack>();
		ret.put(Integer.valueOf(0), item);
		return ret;
	}

	 /**
     * @param minecartAction the minecartAction to set
     */
	public void setMinecartAction(DropChestMinecartAction minecartAction) {
		this.minecartAction = minecartAction;
	}

    /**
     * @return the minecartAction
     */
	public DropChestMinecartAction getMinecartAction() {
		return this.minecartAction;
	}
	
	@SuppressWarnings("deprecation")
	public void setRedstone(boolean value) {
		Block below = getBlock().getRelative(BlockFace.DOWN);
		if (below.getType() == Material.LEVER) {
			byte data = below.getData();
			if (value)
				data = 13;
			else {
				data = 5;
			}
			below.setData(data);
		}
	}

	public void triggerRedstone() {
		setRedstone(true);
		Timer timer = new Timer();
		timer.schedule(new RedstoneTrigger(this), 1000L);
	}

	public void dropAll() {
		if (isProtect()) {
			return;
		}
		World world = getBlock().getWorld();
		Location loc = getBlock().getLocation();
		try
		{
			for (int i = 0; i < this.containerBlock.getInventory().getSize(); i++) {
				ItemStack item = this.containerBlock.getInventory().getItem(i);
					if (item.getAmount() != 0) {
						world.dropItem(loc, item);
						this.containerBlock.getInventory().clear(i);
					}
			}
		}
		catch (Exception e)
		{
			// TODO: don't know why this happens, just quick and dirty
		}
		Date date = new Date();
		this.lastRedstoneDrop = date.getTime();
	}

	private void load(String loadString, String fileVersion) {
		if (!fileVersion.equalsIgnoreCase("0.0")) {
			if ((fileVersion.equals("0.1"))
					|| (fileVersion.equals("0.2"))
					|| (fileVersion.equals("0.3"))
					|| (fileVersion.equals("0.4"))
					|| (fileVersion.equals("0.5"))
					|| (fileVersion.equals("0.6"))
					|| (fileVersion.equals("0.7"))
					|| (fileVersion.equals("0.8"))) {
				String[] splt = loadString.split(";");
				if (splt.length >= 1) {
					String[] data = splt[0].split(",");

					if ((splt.length == 2) || (splt.length == 4)) {
						for (int ft = 1; ft < splt.length; ft++) {
							String[] filters = splt[ft].split(",");
							FilterType type = null;
							switch (ft) {
							case 1:
								type = FilterType.SUCK;
								break;
							case 2:
								type = FilterType.PULL;
								break;
							case 3:
								type = FilterType.PUSH;
								break;
							default:
								type = FilterType.SUCK;
							}

							List<FilterItem> fi = getFilter(type);
							for (int i = 0; i < filters.length; i++)
							{
								if (!filters[i].isEmpty()) {
									FilterItem m = null;
									try {
										m = new FilterItem(filters[i]);
										fi.add(m);
									} catch (IllegalArgumentException e) {
										this.plugin.log.log(Level.WARNING, "Error loading a DropChest!");
									}
								}
							}
						}
					}
					if (data.length >= 6) {
						World world = null;
						Double x = Double.valueOf(data[0]);
						Double y = Double.valueOf(data[1]);
						Double z = Double.valueOf(data[2]);
						this.radius = Integer.valueOf(data[3]).intValue();
						try {
							setMinecartAction(DropChestMinecartAction.valueOf(data[5]));
						} catch (IllegalArgumentException e) {
							setMinecartAction(DropChestMinecartAction.IGNORE);
						}
						World.Environment env = World.Environment.NORMAL;
						if (fileVersion.equals("0.3")) {
							env = World.Environment.valueOf(data[6]);
						}
						if (!fileVersion.equalsIgnoreCase("0.1")) {
							world = this.plugin.getServer().getWorld(data[4]);
							if (world == null) {
								world = this.plugin.getServer().createWorld(new WorldCreator(data[4]).environment(env));
							}
						}
						if (fileVersion.equals("0.4")) {
							this.id = Integer.valueOf(data[7]).intValue();
							currentId = Math.max(currentId, this.id + 1);
						} else {
							this.id = (currentId++);
						}
						if ((fileVersion.equals("0.6"))
								|| (fileVersion.equals("0.7"))
								|| ((fileVersion.equals("0.8")) && (data.length >= 9))) {
							setName(data[8]);
						}
						if ((fileVersion.equals("0.7"))
								|| ((fileVersion.equals("0.8")) && (data.length >= 11))) {
							setOwner(data[9]);
							setProtect(Boolean.valueOf(data[10]).booleanValue());
						}
						if (fileVersion.equals("0.8")) {
							setDelay(Integer.valueOf(data[11]).intValue());
						}
						if (world != null) {
							Block b = world.getBlockAt((int) x.doubleValue(),
									(int) y.doubleValue(),
									(int) z.doubleValue());
							if (acceptsBlockType(b.getType())) {
								this.containerBlock = ((Chest) b.getState());
								this.loc = b.getLocation();
								if (this.containerBlock == null) {
									this.loadedProperly = false;
									System.out.println("Chest is null...");
								}
							} else {
								System.out.println("Block is not accepted!");
								this.loadedProperly = false;
							}
						} else {
							System.out.println("World not found!");
							this.loadedProperly = false;
						}
					} else {
						System.out.println("Number of columns not accepted!");
						this.loadedProperly = false;
					}
				} else {
					System.out.println("Number of columns not accepted!");
					this.loadedProperly = false;
				}
			} else {
				System.out.println("File has invalid version: " + fileVersion);
				this.loadedProperly = false;
			}
		}
	}

	public String save() {
		// VERSION!!!! 0.7
		String line = "";
		line = String.valueOf(this.loc.getX()) + ","
				+ String.valueOf(this.loc.getY()) + ","
				+ String.valueOf(this.loc.getZ()) + ","
				+ String.valueOf(getRadius()) + ","
				+ String.valueOf(this.loc.getWorld().getName());
		line = line + ",";
		line = line + ","
				+ String.valueOf(this.loc.getWorld().getEnvironment());
		line = line + "," + String.valueOf(this.id);
		line = line + "," + this.name;
		line = line + "," + getOwner();
		line = line + "," + String.valueOf(isProtect());
		line = line + "," + getDelay();

		//Filter saving
		for (FilterType type : FilterType.values()) {
			line = line + ";";
			int i = 0;
			for (FilterItem m : getFilter(type)) {
				if (i > 0) {
					line = line + ",";
				}
				line = line + m.toString();
				i++;
			}
			if (getFilter(type).size() == 0) {
				line = line + type.toString();
			}
		}
		line = line + "\n";
		return line;
	}

	public boolean isLoadedProperly() {
		return this.loadedProperly;
	}

	public Block getBlock() {
		return this.loc.getBlock();
	}

	public void setBlock(Block block) {
		this.loc = block.getLocation();
	}

	public static boolean acceptsBlockType(Material m) {
		return (m == Material.CHEST)
				|| (m == Material.DISPENSER)
				|| (m == Material.FURNACE)
				|| (m == Material.BURNING_FURNACE);
	}

	public int getId() {
		return this.id;
	}

	public String listString() {
		String ret = getName();
		double p = getPercentFull();
		ret += " | " + (int) (p * 100.0D) + "%";
		ret += " | " + String.valueOf(getFilter(FilterType.SUCK).size()
						+ getFilter(FilterType.PUSH).size()
						+ getFilter(FilterType.PULL).size()) + " ";
		ret += " | " + String.valueOf(getRadius());
		return ret;
	}
	
	@SuppressWarnings("deprecation")
	public void minecartAction(StorageMinecart storage) {
		Chest chest = getChest();
		Inventory chinv = chest.getInventory();
		Inventory miinv = storage.getInventory();

		for (int i = 0; i < chinv.getSize(); i++) {
			ItemStack items = chinv.getItem(i);
			if((items != null) && (items.getAmount() != 0))
			{
				MaterialData items_matdata = new MaterialData(items.getType().getId(), items.getData().getData());
				
				for (FilterItem m : (List<FilterItem>) this.filter.get(FilterType.PUSH))
				{
					if (m.equals(items_matdata))
					{
						HashMap<Integer,ItemStack> hash = miinv.addItem(new ItemStack[] { items });
						if (hash.size() != 0) {
							ItemStack ret = (ItemStack) hash.get(Integer.valueOf(0));
							items.setAmount(ret.getAmount());
							chinv.setItem(i, items);
						} else {
							chinv.clear(i);
						}
					}
				}
			}
		}

		for (int i = 0; i < miinv.getSize(); i++)
			if (miinv.getItem(i) != null) {
				ItemStack items = miinv.getItem(i).clone();
				if((items != null) && (items.getAmount() != 0))
				{
					MaterialData items_matdata = new MaterialData(items.getType().getId(), items.getData().getData());
					
					for (FilterItem m : (List<FilterItem>) this.filter.get(FilterType.PULL))
					{
						if (m.equals(items_matdata))
						{
							HashMap<Integer,ItemStack> hash = addItem(items, FilterType.PULL);
							if (hash.size() != 0) {
								ItemStack ret = (ItemStack) hash.get(Integer.valueOf(0));
								items.setAmount(ret.getAmount());
								miinv.setItem(i, items);
							} else {
								miinv.clear(i);
							}
						}
					}
				}
			}
	}
	
	public String info() {
		String ret = ChatColor.WHITE.toString();
		String filterString = "";
		ret = ret + "ID: " + ChatColor.YELLOW + getId() + ChatColor.WHITE
				+ " Name: " + ChatColor.YELLOW + getName() + ChatColor.WHITE
				+ " Radius: " + ChatColor.YELLOW + getRadius()
				+ ChatColor.WHITE + " Owner: " + ChatColor.YELLOW + getOwner() + "\n";
		for (FilterType type : FilterType.values()) {
			List<FilterItem> filter = getFilter(type);
			if (filter.size() != 0) {
				filterString = filterString + ChatColor.AQUA + type.toString() + ":\n";
				boolean useId = false;
				if (filter.size() < 5)
					useId = false;
				else {
					useId = true;
				}
			
				for (int i = 0; i < filter.size(); i++) {
					filterString = filterString + ChatColor.YELLOW.toString();
					if (useId)
						filterString = filterString + filter.get(i).toString();
					else {
						filterString = filterString + filter.get(i).getDisplayName()+"("+filter.get(i).toString()+")";
					}
					if (i + 1 != filter.size())
						filterString = filterString + ChatColor.WHITE + ", ";
					else {
						filterString = filterString + ChatColor.WHITE + "\n";
					}
				}
				
			}
		}
		if (!filterString.equals("")) {
			ret = ret + ChatColor.AQUA + "Filters:\n";
			ret = ret + filterString;
		}
		return ret;
	}

	public boolean isChest() {
		return getBlock().getType().equals(Material.CHEST);
	}

	public boolean isFurnace() {
		return (getBlock().getType().equals(Material.FURNACE))
				|| (getBlock().getType().equals(Material.BURNING_FURNACE));
	}

	public boolean isDispenser() {
		return getBlock().getType().equals(Material.DISPENSER);
	}
	
	public void setName(String name) {
		this.name = name;
	}

	public String getName() {
		if (this.name.isEmpty()) {
			return "#" + String.valueOf(this.id);
		}
		return this.name;
	}

	public Location getLocation() {
		return this.loc;
	}

	public void setOwner(String owner) {
		this.owner = owner;
	}

	public String getOwner() {
		return this.owner;
	}

	public void setProtect(boolean protect) {
		this.protect = protect;
	}

	public boolean isProtect() {
		if (this.plugin.config.isProtectionEnabled()) {
			return this.protect;
		}
		return false;
	}

	public long getLastRedstoneDrop() {
		return this.lastRedstoneDrop / 1000L;
	}

	public DropChestPlayer getOwnerDCPlayer() {
		return DropChestPlayer.getPlayerByName(getOwner());
	}

	public void save(Map<String, Object> chest) {
		chest.put("id", Integer.valueOf(getId()));
		chest.put("name", getName());
		chest.put("owner", getOwner());
		chest.put("delay", Integer.valueOf(getDelay()));
		chest.put("protect", Boolean.valueOf(isProtect()));
		chest.put("radius", Integer.valueOf(getRadius()));
		chest.put("location", locationToString(this.loc));
		Map<String, Object> filters = new HashMap<String, Object>();
		for (FilterType type : FilterType.values()) {
			ArrayList<String> filter = new ArrayList<String>();

			for (FilterItem mat : getFilter(type)) {
				filter.add(mat.toString());
			}
			
			filters.put(type.toString(), filter);
		}
		chest.put("filter", filters);
	}

	@SuppressWarnings({ "unchecked"})
	private void load(Map<String, Object> loadMap) {
		this.id = ((Integer) loadMap.get("id")).intValue();
		setName((String) loadMap.get("name"));
		setOwner((String) loadMap.get("owner"));
		setDelay(((Integer) loadMap.get("delay")).intValue());
		setProtect(((Boolean) loadMap.get("protect")).booleanValue());
		setRadius(((Integer) loadMap.get("radius")).intValue(),true);
		this.loc = locationFromString((ArrayList<String>)loadMap.get("location"));

		if (this.id >= currentId) {
			currentId = this.id + 1;
		}
		
		Map<String, Object> filters = (Map<String, Object>) loadMap.get("filter");
        for(String typename:filters.keySet()){
            FilterType type = FilterType.valueOf(typename);
            List<FilterItem> filter = getFilter(type);
            ArrayList<String> items = (ArrayList<String>)filters.get(typename);
            for(String item:items)
            {
                filter.add(new FilterItem(item));
            }
        }
	}

	private String[] locationToString(Location loc) {
		String[] ret = { loc.getWorld().getName(),
				loc.getWorld().getEnvironment().toString(),
				Integer.toString(loc.getBlockX()),
				Integer.toString(loc.getBlockY()),
				Integer.toString(loc.getBlockZ()) };

		return ret;
	}

	private Location locationFromString(ArrayList<String> args) {
		World w = Bukkit.getServer().createWorld(
				new WorldCreator((String) args.get(0))
						.environment(World.Environment.valueOf((String) args
								.get(1))));
		int x = Integer.valueOf((String) args.get(2)).intValue();
		int y = Integer.valueOf((String) args.get(3)).intValue();
		int z = Integer.valueOf((String) args.get(4)).intValue();
		return new Location(w, x, y, z);
	}
}